package hyl.ext.web.ms;

import java.io.File;
import java.io.Serializable;
import java.security.Key;

/**
 * 微服务属性
 */

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import hyl.base.net.nio.NIOReader;
import hyl.core.MyCharset;
import hyl.core.MyFun;
import hyl.core.conf.MyConfig;
import hyl.core.db.MyDB;
import hyl.core.fun.MyByte;
import hyl.core.io.MyFile;
import hyl.core.io.MyPath;
import hyl.core.run.MyRun;
import hyl.core.safe.MyAes;
import hyl.core.safe.MyBase64;
import hyl.core.safe.MySecret;
import hyl.core.safe.MyZip;

/**
 * 用于存放 各个系统的 属性和通用函数
 * 
 * @author 37798955@qq.com
 *
 */
public class BInfo implements Serializable {
	/**
	 * 用于客户端信息 和服务端信息之间交互
	 */
	private static final long serialVersionUID = 1L;

	// 客户端的信息需要发送到服务端
	Map<String, String> pub信息 = new HashMap<>(); // 微服务程序信息,交互内容 其他属性不需要交互
	// 其他属性不需要发送到服务端,但是需要存储到本地 文件中
	// 服务端则需要保存到文件中
	/**
	 * TMS系统 ip地址
	 */
	String a_ip;
	/**
	 * sso系统的域名
	 */
	String aUrl = null;
	// String bUrl = null;
	// 以上是A系统必须保存的属性
	/**
	 * 这个key 是 b 连接 a服务器时 a服务器动态提供给 b的密钥
	 * 
	 * 
	 */
	String keystr = null;
	/**
	 * 监听端口, 用于单点系统监听
	 */
	int a_port = 0;
	/**
	 * 连接服务中心时,服务中心反馈的remote ip ,每次启动都要等待服务器反馈
	 */
	String b_ip = null;
	String appid = null;
	String b首页 = null;
	String b异常页 = null;
	String b通知页 = null;
	String b登录页 = null;
	// 以上是B系统必须保存的属性

	// 服务端专有属性 1 表示可以连接 0 表示阻止接入
	int istate = 1;
	/**
	 * 客户端每次PING的心跳时间 只保存与服务端中
	 */
	long ptime = 0L;
	static final byte D停止 = (byte) 0;
	static final byte D就绪 = (byte) 1;
	static final byte D运行中 = (byte) 2;
	static final byte D异常 = (byte) 3;

	public static final byte S未审批 = (byte) 1;
	public static final byte S禁用 = (byte) 0;
	public static final byte S已审批 = (byte) 2;

	byte state = D停止;// 客户端运行状态: 0: 停止,1: 准备继续, 2: 运行中 3异常
	String err = null;// 当前错误信息 发送给服务中心存储 ,异常后 通知程序
	// String pubkey = "";// 所有客户端都有相同的公钥
	transient File msm0 = null; // 不初始化时 为空

	/**
	 * 消息通道
	 */
	transient NIOReader _reader;

	public boolean isConnected() {
		return _reader.isConnected();
	}

	/**
	 * aes key
	 */
	transient Key _bkey = null;// des 密钥 b连接 a时动态分配

	MyAes _maes = new MyAes();
	/**
	 * 服务器类型<br>
	 * A 表示 单点A型服务器<br>
	 * B 表示 连接单点服务器的 B型服务器
	 *
	 */

	char type = 'B';

	public BInfo() {

	}

	public String getAppid() {
		return appid;
	}

	public boolean eqAppid(String appid2) {
		if (MyFun.isBlank(appid) || MyFun.isBlank(appid2))
			return false;
		return appid.equals(appid2);
	}

	public void setAppid(String appid) {
		this.appid = appid;
	}

	public String getKeystr() {
		return keystr;
	}

	public Key getAesKey() {
		return _bkey;
	}

	/**
	 * 每个一段时间更新密钥
	 */
	public String upKey() {
		setKeystr(MyFun.getMsDesc());
		save();
		return keystr;
	}

	public void setKeystr(String key) {
		keystr = key;
		_bkey = MySecret.createKey("AES", keystr);
	}

	public String getA_ip() {
		return a_ip;
	}

	public String getB_ip() {
		return b_ip;
	}

	public int getAPort() {
		return a_port;
	}

	public byte getState() {
		return state;
	}

	public long getPtime() {
		return ptime;
	}

	public void setPtime(long ptime) {
		this.ptime = ptime;
	}

	public Map<String, String> getPub信息() {
		return pub信息;
	}

	public void setPub信息(Map<String, String> pub信息) {
		this.pub信息 = pub信息;
	}

	public String getB首页() {
		return pub信息.get("BURL") + MyFun.nvlStr(b首页, "");
	}

	public String getB异常页() {
		return pub信息.get("BURL") + MyFun.nvlStr(b异常页, "");
	}

	public String getB通知页() {
		return pub信息.get("BURL") + MyFun.nvlStr(b通知页, "");
	}

	/**
	 * 客户端启动初始化
	 */
	// 公钥单独文件存放
	public void init(File msm) {

		msm0 = msm;
		if (msm.getName().equals("msa")) {
			type = 'A';
		}
		// System.out.println(type);

		Map<String, String> iniMap = MyConfig.loadIni(msm0, MyCharset._UTF8);

		// appid 每个微服务都是独立的
		// 客户端如果没有 初始化的时候自动生成 ,需要服务中心提交注册
		appid = MyFun.nvlStr(iniMap.get("APPID"), MyFun.getUUID());

		// 微服务进程编号 每次启动从操作系统获取
		String psid = String.valueOf(MyRun.getProcessID());

		// pubkey = iniMap.get("PUBKEY"); //不需要上传给服务器
		Properties props = System.getProperties();
		// MyFun.println(msm0.getAbsolutePath(), JSON.toJSONString(iniMap));
		// StringBuilder sb =new StringBuilder();
		// map中是与服务器共有的信息

		if (type == 'A') {
			// "#监听端口ip, 中心与客户端同一台主机时,端口不能重复"
			a_port = MyFun.str2int(iniMap.get("PORT"));
			a_port = (a_port < 1000) ? 6220 : a_port;

			// sso系统 web项目根页
			// B站点不用填,登录A以后, A 会重新发送给B
			aUrl = iniMap.get("A_URL");
			// MyFun.print(a_port,aUrl);
		} else {
			a_ip = iniMap.get("A_IP");
			b首页 = iniMap.get("B首页");
			b异常页 = iniMap.get("B异常页");
			b通知页 = iniMap.get("B通知页");

			// 每次各系统B接入A系统,A就会重新分发密钥给各系统用来签名数据 ,确保该数据来自b
			// B系统的密码 可以用来签名 也可以用来进行aes加密
			setKeystr(MyFun.nvlStr(iniMap.get("KEY"), MyFun.getMsDesc()));
			// B站的 web项目根页
			put("BURL", MyFun.nvlStr(iniMap.get("BURL"), "#单点登录站点不用填 ,应用程序填 http://www.xxx.com"));
		}

		// 两个相同的微服务名称是一致的
		put("APPNAME", MyFun.nvlStr(iniMap.get("APPNAME"), "#输入项目名称"));// 必填 项目
		put("APPMAN", MyFun.nvlStr(iniMap.get("APPMAN"), "#输入项目负责人"));// 必填 项目
		put("APPTEL", MyFun.nvlStr(iniMap.get("APPTEL"), "#输入项目负责人手机"));// 必填 项目
		// 两个微服务名称一致 但是可能版本是不同的
		put("APPVER", MyFun.nvlStr(iniMap.get("APPVER"), "#输入项目版本号"));// 非必填 项目版本
		put("PROGVER", "JDK" + props.getProperty("java.version"));
		put("OS", props.getProperty("os.name"));
		put("OSBIT", props.getProperty("os.arch"));
		put("OSVER", props.getProperty("os.version"));
		put("PSID", psid);
		put("WORKDIR", MyPath.getRootPath().replace("\\", "/"));
		put("OSUSR", props.getProperty("user.name"));
		// 如果key 的版本发生变化 ,与服务中心的keyver 不一致 就说明key需要更新,否则不更新
		// put("KEYVER", MyFun.nvlStr(iniMap.get("KEYVER"), "#输入key的版本号"));
		// put("SIGN", iniMap.get("SIGN"));// 签名确保信息不被篡改

		ready();
		// saveCli();//不能加否则会在 sso中心存储信息
	}

	public String getAURL() {
		return aUrl;
	}

	public String getBURL() {
		return pub信息.get("BURL");
	}

	public String getAPPName() {
		return pub信息.get("APPNAME");
	}

	/**
	 * 保存在客户端
	 */
	public void save() {
		if (msm0 == null)
			return;
		MyConfig.writeIni(msm0, pub信息);
		// 私有信息只保存在本地文件中

		if (type == 'B') {
			byte[] bb = MyFun.join(//
					"APPID=", appid, "\r\n", //
					"A_IP=", MyFun.nvlStr(a_ip, "#填写单点服务器所在ip"), "\r\n", // tmsa .ip服务地址
					"B_IP=", b_ip, "\r\n", //
					"A_URL=", aUrl, "\r\n", //
					"ERR=", err, "\r\n", //
					"PTIME=", ptime, "\r\n", //
					"B首页=", MyFun.nvlStr(b首页, "#如果b可以支持单点登录 填写"), "\r\n", //
					"B异常页=", MyFun.nvlStr(b异常页, "#如果b可以支持单点登录 填写"), "\r\n", //
					"B通知页=", MyFun.nvlStr(b通知页, "#如果b可以支持单点登录 填写"), "\r\n", //
					"KEY=", keystr, "\r\n" //
			).getBytes();
			MyFile.appendBytes(msm0, bb);
		} else { // type='A'
			byte[] bb = MyFun.join(//
					"APPID=", appid, "\r\n", //
					"A_URL=", aUrl, "\r\n", //
					"PORT=", a_port, "\r\n", //
					"ERR=", err, "\r\n", //
					"PTIME=", ptime, "\r\n" //
			).getBytes();
			MyFile.appendBytes(msm0, bb);
		}

	}

	public void ping() {
		this.ptime = MyFun.getMs();
		if (this.state != D运行中)
			this.state = D运行中;
	}

	public void ready() {
		this.state = D就绪;
		this.err = null;
		this.ptime = MyFun.getMs();
	}

	public void err(String err) {
		this.err = err;
		this.state = D异常;
		this.ptime = MyFun.getMs();
	}

	public void stop() {
		this.err = null;
		this.state = D停止;
		this.ptime = MyFun.getMs();
	}

	public String toDate() {
		return MyFun.Long2DateStr(this.ptime);
	}

	//
	public void connected(String[] 返回参数) {
		b_ip = 返回参数[0];
		if (返回参数.length > 1)
			aUrl = 返回参数[1];
		save();
	}

	public String get(String key) {
		return pub信息.get(key);
	}

	public void put(String key, String value) {
		pub信息.put(key, value);
	}

	public void remove(String key, String value) {
		pub信息.remove(key, value);
	}

	/**
	 * 用于客户端编译 适用于 byte[] 传输
	 * 
	 * @return
	 */
	public byte[] encode() {
		// byte[] b1=kryo.serialize(_信息);
		// MyFun.print(b1.length,MyByte.bytesToHex(b1));
		return MyZip.do压缩(MyByte.mapS2U8Bytes(pub信息), 2);
	}

	/**
	 * 用于客户端编译 适用于 url传输
	 * 
	 * @return
	 */
	public String encodeB64() {
		byte[] bb = MyByte.mapS2U8Bytes(pub信息);
		return MyBase64.encodeUri(bb);
	}

	/**
	 * 用于服务端解析 适用于 byte[] 传输
	 * 
	 * @param bb
	 */

	public void decode(byte[] bb) {
		bb = MyZip.do解压(bb);
		pub信息 = MyByte.u8bytes2MapS(bb);
	}

	/**
	 * 用于服务端解析 适用于 url传输
	 * 
	 * @param str
	 */

	public void decodeB64(String str) {
		byte[] bb = MyBase64.decodeUri(str);
		pub信息 = MyByte.u8bytes2MapS(bb);
	}

	////////////////////////////////////////////////
	/**
	 * 用于服务端解析 适用于 byte[] 传输
	 * 
	 * @param bb
	 */
	public static Map<String, String> decodeToMap(byte[] bb) {
		bb = MyZip.do解压(bb);
		return MyByte.u8bytes2MapS(bb);
	}

	/**
	 * 用于服务端解析 适用于 url传输
	 * 
	 * @param str
	 */

	public static Map<String, String> decodeB64ToMap(String str) {
		byte[] bb = MyBase64.decodeUri(str);
		return MyByte.u8bytes2MapS(bb);

	}

	public String aes加密64(String 明文) {
		return MyAes.f加密64(明文, _bkey);
	}

	public String aes解密64(String 密文) {
		return MyAes.f解密64(密文, keystr);
	}

	public byte[] aes加密(String 明文) {
		return MyAes.f加密(明文.getBytes(), _bkey);
	}

	public String aes解密(byte[] 密文) {
		return new String(MyAes.f解密(密文, _bkey));
	}

	public void insertRow(MyDB db) {
		String sql = "insert into app (appid,l0,t0,t1,t2,  t3,t5,t6,t7,t8,  t9,t10,t11,t12,t13,  t14) values(?,?,?,?,?,  ?,?,?,?,?, ?,?,?,?,?, ?)";
		Object[] params = { appid, ptime, get("APPNAME"), get("APPMAN"), get("APPTEL"), //
				get("BURL"), get("APPVER"), get("PROGVER"), get("WORKDIR"), get("OS"), //
				get("OSBIT"), get("OSVER"), get("OSUSR"), keystr, b_ip, //
				get("PSID") };//
		// bapp的状态不保存,从内存读取 只要是动态的就不写入数据库

		db.executeUpdate(sql, params);
	}

	public void updateRow(MyDB db) {
		String sql = "update app set l0=?,t0=?,t1=?,t2=?,t3=?,  t5=?,t6=?,t7=?,t8=?,t9=?,"
				+ "t10=?,t11=?,t12=?,t13=?,t14=? where appid=?";
		Object[] params = { ptime, get("APPNAME"), get("APPMAN"), get("APPTEL"), get("BURL"), //
				get("APPVER"), get("PROGVER"), get("WORKDIR"), get("OS"), get("OSBIT"), //
				get("OSVER"), get("OSUSR"), keystr, b_ip, get("PSID"), //
				appid };//
		db.executeUpdate(sql, params);
		// MyFun.print("更新");
	}

	public void loadObjects(Object[] 属性集) {
		// appid,l0,t0,t1,t2, t3,t4,t5,t6,t7, t8,t9,t10,t11,t12, t13,t14,i0
		// appid,l0,t15,t1,t2, t3,t4,t5,t6,t7, t8,t9,t10,t11,t12, t13,t14,i0
		appid = MyFun.obj2Str(属性集[0]);
		ptime = MyFun.obj2Long(属性集[1]);
		put("APPNAME", MyFun.obj2Str(属性集[2]));
		put("APPMAN", MyFun.obj2Str(属性集[3]));
		put("APPTEL", MyFun.obj2Str(属性集[4]));

		put("BURL", MyFun.obj2Str(属性集[5]));
		put("APPCLS", MyFun.obj2Str(属性集[6]));
		put("APPVER", MyFun.obj2Str(属性集[7]));
		put("PROGVER", MyFun.obj2Str(属性集[8]));
		put("WORKDIR", MyFun.obj2Str(属性集[9]));

		put("OS", MyFun.obj2Str(属性集[10]));
		put("OSBIT", MyFun.obj2Str(属性集[11]));
		put("OSVER", MyFun.obj2Str(属性集[12]));
		put("OSUSR", MyFun.obj2Str(属性集[13]));
		keystr = MyFun.obj2Str(属性集[14]);

		b_ip = MyFun.obj2Str(属性集[15]);
		put("PSID", MyFun.obj2Str(属性集[16]));
		istate = MyFun.obj2Int(属性集[17]);

	}

	public int getIstate() {
		return istate;
	}

	public void setIstate(int istate) {
		this.istate = istate;
	}

}
